package com.agenda.client;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import com.agenda.client.listeners.OuvidorStatusListener;
import com.agenda.client.listeners.RecebimentoClienteListener;
import com.agenda.comum.EnvioInformacao;
import com.agenda.concorrencia.Semaforo;
import com.agenda.model.Requisicao;
import com.agenda.model.RequisicaoEnum;
import com.agenda.model.RespostaRequisicao;
import com.agenda.model.StatusServidor;

public class ClienteServidor {

	private static ClienteServidor INSTANCIA;

	private Socket sck = null;
	private String ip = "";
	private int porta = 0;
	private OuvidorStatusListener ouvidorStatus;
	private Timer verificadorStatusTime;
	private Semaforo semaforoEnvio;

	private ServerSocket servidorSckCliente = null;

	private ArrayList<RecebimentoClienteListener> recebedoresListener = new ArrayList<RecebimentoClienteListener>();

	public static int PORTA_RECEBIMENTO = 7004;
	//DEVE SER A MESMA PORTA POREM COMO SÒ TEM UMA MAQUINA FORAM CRIADAS DUAS PORTAS
	public static int PORTA_OUVIDOR = 7005;

	private ClienteServidor() {
		semaforoEnvio = new Semaforo();
	}

	public void addRecebimentoListener(RecebimentoClienteListener recebimento) {
		recebedoresListener.add(recebimento);

		System.out.println("ADICIONOU AOS RECEBEDORES  --- ------ RECEBEDORES " + recebedoresListener);
	}

	public void criarConexao() {
		try {
			if (sck == null) {

				sck = new Socket(ip, porta);
				servidorSckCliente = new ServerSocket(PORTA_OUVIDOR);

				iniciarRecebimento();

				iniciarVerificadorStatus();

			} else
				System.err.println("A conexão é diferente de NULO, REINICIE O CLIENTE");
			//Cria o socket com o recurso desejado na porta especificada  

		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("Algum problema ocorreu ao criar ou enviar dados pelo socket.");
		}

	}

	private void iniciarRecebimento() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Socket sck = servidorSckCliente.accept();
					if (sck != null) {
						new Thread(new RequisicaoAoClienteThread(sck)).start();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}

			}
		}).start();

	}

	private void iniciarVerificadorStatus() {

		verificadorStatusTime = new Timer();
		verificarStatus();
	}

	private void verificarStatus() {

		if (ouvidorStatus != null) {

			try {
				RespostaRequisicao resposta = enviarInformacao(new EnvioInformacao() {
					@Override
					public Requisicao getRequisicao() {
						return new Requisicao(RequisicaoEnum.VERIFICAR_STATUS.getId());
					}

					@Override
					public void enviar(OutputStream output) throws Exception {
						// Não precisa enviar nada para o servidor.
					}

					@Override
					public void setSocket(Socket socket) {
					}
				});

				//System.out.println("RESPOSTA DO VERICADOR - " + resposta);

				StatusServidor status = (StatusServidor) lerObjeto();

				ouvidorStatus.listaClienteAtualizada(status.getUsuariosOnLine(), status.getStatusUsuariosOnLine());

				//System.out.println(status.getStatusUsuariosOnLine());

				ouvidorStatus.listaCompromissos(status.getCompromissos());
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				liberarComunicacao();
			}
		}
		verificadorStatusTime.schedule(new TimerTask() {
			@Override
			public void run() {
				verificarStatus();
			}
		}, 5000);

	}

	public Object lerObjeto() throws Exception {
		ObjectInputStream dataInputStream = new ObjectInputStream(sck.getInputStream());
		return dataInputStream.readObject();
	}

	public RespostaRequisicao enviarInformacao(EnvioInformacao envioInformacao) throws Exception {

		//System.out.println("ENVIANDO REQUISIÇÃO  TRAVADO == " + envioInformacao.getRequisicao().getRequisicao());
		semaforoEnvio.DOWN();
		//System.out.println("ENVIANDO REQUISIÇÃO  DESTRAVADO == " + envioInformacao.getRequisicao().getRequisicao());
		if (sck != null) {
			envioInformacao.setSocket(sck);
			//System.out.println("ENVIANDO REQUISIÇÃO  == " + envioInformacao.getRequisicao().getRequisicao());
			sck.getOutputStream().write(envioInformacao.getRequisicao().getRequisicao());
			envioInformacao.enviar(sck.getOutputStream());

			//System.out.println("Aguardando resposta");
			ObjectInputStream input = new ObjectInputStream(sck.getInputStream());
			//System.out.println("Respondeu");
			Object obj = input.readObject();
			RespostaRequisicao resposta = (RespostaRequisicao) obj;
			//System.out.println("Respondeu " + resposta);
			return resposta;
		}
		return new RespostaRequisicao(true, "Conexão com o servidor não inicializada.");

	}

	public void liberarComunicacao() {
		semaforoEnvio.UP();
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public void setPorta(int porta) {
		this.porta = porta;
	}

	public Boolean lerBoolean() throws Exception {
		DataInputStream dataInputStream = new DataInputStream(sck.getInputStream());

		return dataInputStream.readBoolean();
	}

	public void registrarOuvidorStatus(OuvidorStatusListener ouvidorStatus) {
		this.ouvidorStatus = ouvidorStatus;
	}

	public static ClienteServidor getINSTANCIA() {
		if (INSTANCIA == null) {
			INSTANCIA = new ClienteServidor();
		}
		return INSTANCIA;
	}

	public synchronized ArrayList<RecebimentoClienteListener> getRecebedoresListener() {
		return recebedoresListener;
	}

	public Socket getSocket() {
		return sck;
	}

}
